package com.lagou.edu.message.server;

import com.alibaba.fastjson.JSON;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.corundumstudio.socketio.listener.ExceptionListener;
import com.corundumstudio.socketio.namespace.Namespace;
import com.corundumstudio.socketio.protocol.Packet;
import com.corundumstudio.socketio.protocol.PacketType;
import com.lagou.edu.common.jwt.JwtUtil;
import com.lagou.edu.message.api.dto.Message;
import com.lagou.edu.message.consts.Constants;
import com.lagou.edu.message.server.store.StoreFacotryProvider;
import com.lagou.edu.message.util.PushUtils;
import com.lagou.edu.message.util.ServerConfigUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.net.SocketAddress;
import java.util.*;

@Slf4j
public class PushServer {

    public static final PushServer pushServer = new PushServer();
    private Namespace pushNamespace;
    private SocketIOServer server;

    private PushServer() {
        final Configuration configuration = new Configuration();
        configuration.setStoreFactory(StoreFacotryProvider.getRedissonStoreFactory());
        configuration.setAuthorizationListener(new UserAuthorizationListener());
        configuration.setOrigin(ServerConfigUtils.instance.getWebSocketOrigin());
        configuration.setPort(ServerConfigUtils.instance.getWebSocketPort());
        configuration.setContext(ServerConfigUtils.instance.getWebSocketContext());

        server = new SocketIOServer(configuration);
        pushNamespace = (Namespace) server.addNamespace(ServerConfigUtils.instance.getWebSocketContext());

        configuration.setExceptionListener(new ExceptionListener() {
            @Override
            public void onEventException(Exception e, List<Object> list, SocketIOClient client) {
                UUID sessionId = client.getSessionId();
                log.error("onEventException, sessionId:{}, roomList:{}", sessionId, client.get(Constants.ROOM), e);
            }

            @Override
            public void onDisconnectException(Exception e, SocketIOClient client) {
                UUID sessionId = client.getSessionId();
                log.error("onDisconnectException, sessionId:{}, roomList:{}", sessionId, client.get(Constants.ROOM), e);
            }

            @Override
            public void onConnectException(Exception e, SocketIOClient client) {
                UUID sessionId = client.getSessionId();
                log.error("onConnectException, sessionId:{}, roomList:{}", sessionId, client.get(Constants.ROOM), e);
            }

            @Override
            public void onPingException(Exception e, SocketIOClient client) {
                try {
                    boolean channelOpen = client.isChannelOpen();
                    SocketAddress remoteAddress = client.getRemoteAddress();
                    UUID sessionId = client.getSessionId();
                    log.error("onPingException, channelopen:{}, sessionId:{}, remoteAddress:{}, roomList:{}", channelOpen, sessionId, remoteAddress, e);
                } catch (Exception e1) {
                    log.error("", e1);
                }
            }

            @Override
            public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
                try {
                    Channel channel = ctx.channel();
                    if (null != channel) {
                        log.error("exceptioncaught, channel:{}, isOpen:{}, remoteAddress:{}", channel.id(), channel.isOpen(), channel.remoteAddress());
                    }
                } catch (Exception ex) {
                    log.error("exceptioncaught ", e);
                }
                return false;

            }
        });
        pushNamespace.addConnectListener(new ConnectListener() {
            @Override
            public void onConnect(SocketIOClient client) {
                try {
                    UUID sid = client.getSessionId();
                    JwtUtil.JwtResult user = UserAuthorizationListener.getUserInfo(client.getHandshakeData());
                    if (user != null) {
                        Set<String> rooms = client.getAllRooms();
                        for (String room : rooms) {
                            client.leaveRoom(room);
                        }
                        String userId = Integer.toString(user.getUserId());
                        List<String> roomList = PushUtils.getRoomList(userId, null, null);
                        for (String room : roomList) {
                            client.joinRoom(room);
                            log.info("userId:{}, sid:{}, join room{}", userId, sid, room);
                        }
                        client.set(Constants.ROOM, roomList);
                    } else {
                        client.disconnect();
                        log.warn("sid:{} has no userId", sid);
                    }
                } catch (Exception e) {
                    log.error("addConnectListener - error ", e);
                }
            }
        });
        pushNamespace.addDisconnectListener(new DisconnectListener() {
            @Override
            public void onDisconnect(SocketIOClient client) {
                UUID sid = client.getSessionId();
                JwtUtil.JwtResult user = UserAuthorizationListener.getUserInfo(client.getHandshakeData());
                if (user != null) {
                    log.info("disconnect userId:{}, userName:{}, sid:{}", user.getUserId(), user.getUserName(), sid);
                } else {
                    client.disconnect();
                    log.info("client get handShakeData is null, disconnect.sid:{}", sid);
                    return;
                }
                String userId = Integer.toString(user.getUserId());
                List<String> roomList = PushUtils.getRoomList(userId, null, null);
                for (String roomStr : roomList) {
                    client.leaveRoom(roomStr);
                }
                try {
                    //离开房间
                    List<String> oldRoomList = client.get(Constants.ROOM);
                    if (null != oldRoomList && oldRoomList.size() > 0) {
                        for (String room : oldRoomList) {
                            if (StringUtils.isBlank(room)) {
                                continue;
                            }
                            client.leaveRoom(room);
                        }
                        client.disconnect();
                    }
                } catch (Exception e) {
                    log.error("leave old room exception,sid:{}}", sid, e);
                } finally {
                    try {
                        client.del(Constants.ROOM);
                        log.error("sid:{},del hash success,field:{}", sid, Constants.ROOM);
                    } catch (Exception e) {
                        log.error("sid:{},del exception,sid:{}}", sid, e);
                    }
                }
            }
        });
    }

    /**
     * 推送消息
     *
     * @param message
     */
    public void push(Message message) {
        Integer userID;
        String json = JSON.toJSONString(message);
        try {
            userID = message.getUserId();
        } finally {

        }

        String room;
        try {
            if (userID == null) {
                throw new NullPointerException();
            }
            room = PushUtils.getRoom(null, userID, null);
        } finally {

        }

        Packet packet = new Packet(PacketType.MESSAGE);
        packet.setSubType(PacketType.EVENT);
        packet.setName("message");

        ArrayList<Object> data = new ArrayList<>();
        data.add(json);
        packet.setData(data);
        packet.setNsp(pushNamespace.getName());

        Iterable<SocketIOClient> roomClients = pushNamespace.getRoomClients(room);

        for (SocketIOClient roomClient : roomClients) {
            roomClient.send(packet);
        }
    }

    /**
     * 同步启动服务；
     */
    public void start() {
        try {
            log.error("Push server starting.............");
            server.start();
        } catch (Exception e) {
            log.error("Push server start failed!", e);
            System.exit(-1);
        }
    }

    /**
     * 停止服务
     */
    public void stop() {
        server.stop();
    }

    public Map<String, Object> getStatus() {
        HashMap<String, Object> status = new HashMap<>();
        status.put("namespace", pushNamespace.getName());   // namespace
        status.put("rooms", pushNamespace.getRooms());
        status.put("clients", pushNamespace.getAllClients().size());
        return status;
    }

}
